import { cardData, monsterData, Card, Monster, Magic, Tragic, Desk, cardMold } from './card';


export interface User {
    id: symbol;
    name: string;
    duel: Duel;
    desk: Desk;
    hand: Hand;
    cemetery: Cemetery;
    filed: Filed;
}

export class User {
    life: number = 8000;
    constructor(duel: Duel, name: string = '神秘决斗者') {
        this.name = name;
        duel.users.push(this);
        this.duel = duel;
        this.hand = new Hand(this);
        this.desk = new Desk(this);
        this.cemetery = new Cemetery(this);
        this.filed = new Filed(this);
    }
    loadDesk(desk: cardMold[]) {
        this.desk.load(desk);
    }
    speak(message) {
        this.duel.msg(message, this);
    }
    get enemy(): User {
        return this.duel.myEnemy(this);
    }
    async draw(num: number = 1) {
        let cards = this.desk.draw(num);
        if (cards.length < num) {
            this.duel.loose(this,'run-out-of-card', '抽光了所有卡片...')
            return;
        }
        this.hand.add(cards);
        this.speak('我抽了'+num+'张卡');
        await this.duel.triger('draw-card', this, {
            num
        });
    }
    async useHand(index: number, effectIndex: number = 0, passive = false) {
        if (!passive) {
            let answer = await this.duel.pipeline('useHand', index);
            if (answer instanceof Function) { answer(); }
        }

        if (this.hand.available(effectIndex)[index]) {
            this.hand.cards[index]._effect_();
        } else {
            this.speak('这张卡不满足发动条件！');
            return false;
        }
    }
    async attack(from: Card, target: Card, passive = false) {
        if (!passive) {
            let answer = await this.duel.pipeline('attack', from, target);
            if (answer instanceof Function) { answer(); }
        }

        await this.duel.triger('declare-attack', from);
        await this.duel.triger('before-attack', from);
        await this.duel.triger('on-damage-stage', from);

        let sub = from.currentData.attack - target.currentData.attack;
        if (sub > 0) {
            target.owner.life -= sub;
            this.duel.triger('damage', target.owner);
            //target.battleBroken();
            await this.duel.triger('monster-battle-broken', target);
            await this.duel.triger('monster-send-to-cemetery', target);
        } else if (sub === 0) {
            //target.battleBroken();
            await this.duel.triger('monster-battle-broken', target);
            await this.duel.triger('monster-send-to-cemetery', target);

            //from.battleBroken();
            await this.duel.triger('monster-battle-broken', from);
            await this.duel.triger('monster-send-to-cemetery', from);
        } else {
            from.owner.life += sub;
            await this.duel.triger('damage', from.owner);
            //from.battleBroken();
            await this.duel.triger('monster-battle-broken', from);
            await this.duel.triger('monster-send-to-cemetery', from);
        }

    }
    async summon(handIndex: number, filedIndex: number, passive = false) {
        if (!passive) {
            let answer = await this.duel.pipeline('summon', handIndex, filedIndex);
            if (answer instanceof Function) { answer(); }
        }

        let monster = this.hand.pick([handIndex])[0];
        monster.summon();
        this.filed.addMonster(filedIndex, monster);
        this.speak('我从手卡召唤 - ' + monster.currentData.name);

        await this.duel.triger('monster-summon', monster);
        await this.duel.triger('monster-summon-succeed', monster);
    }
    async specialSummonFromHand(handIndex: number, filedIndex: number, passive = false) {
        if (!passive) {
            let answer = await this.duel.pipeline('specialSummonFromHand', handIndex, filedIndex);
            if (answer instanceof Function) { answer(); }
        }

        let monster = this.hand.pick([handIndex])[0];
        monster.specialSummon();
        this.filed.addMonster(filedIndex, monster);
        this.speak('我特殊召唤 - ' + monster.currentData.name);

        await this.duel.triger('monster-special-summon', monster);
        await this.duel.triger('monster-special-summon-succeed', monster);
    }
}




export interface Hand {
    cards: Card[];
    num: number;
    owner: User;
}

export class Hand {
    maxLen = 6;
    name = 'hand';
    constructor(owner: User) {
        this.owner = owner;
        this.cards = [];
        this.num = 0;
    }
    add(cards: Card[]) {
        this.num += cards.length;
        for (let i in cards) {
            cards[i].place = this;
        }
        this.cards = this.cards.concat(cards);
    }
    pick(indexs: number[]) {
        indexs = indexs.sort();
        let retain = [];
        let dele = [];
        let delIndex = indexs.unshift();
        for (let index = 0; index < this.cards.length; index++) {
            if (index === delIndex) {
                dele.push(this.cards[index])
                delIndex = indexs.unshift();
            } else {
                retain.push(this.cards[index]);
            }
        }
        this.cards = retain;
        this.num = retain.length;

        return dele;
    }
    throw(indexs: number[], caller: any) {
        let dele = this.pick(indexs);

        this.owner.duel.triger('hand-to-cemetery', caller);
        this.owner.cemetery.recieve(dele, caller);
    }
    available(index: number = 0) {
        let res = [];
        this.cards.forEach((card) => {
            res.push(card.available(index, 'hand-effect'));
        })

        return res;
    }
}




export interface Filed {
    owner: User;
    monsters: Monster[];
    matragics: any[];
}

export class Filed {
    name = 'filed';
    constructor(owner: User) {
        this.owner = owner;
        this.monsters = new Array(5);
        this.matragics = new Array(5);
    }
    get monsterList(): Monster[] {
        return this.monsters.reduce((list, item, index) => {
            if (item) {
                list.push([item, index])
            }
            return list;
        }, [])
    }
    get matragicsList(): any[] {
        return this.matragics.reduce((list, item, index) => {
            if (item) {
                list.push([item, index])
            }
            return list;
        }, [])
    }
    addMonster(index: number, monster: Monster): boolean {
        if (this.monsters[index]) {
            return false;
        }

        this.monsters[index] = monster;
        return true;
    }
    addMatragic(index: number, matragic: Magic | Tragic): boolean {
        if (this.matragics[index]) {
            return false;
        }

        this.matragics[index] = matragic;
        return true;
    }
    availableMonsters(index: number = 0) {
        let res = [];
        this.monsters.forEach((card) => {
            res.push(card.available(index, 'filed-effect'));
        })

        return res;
    }
    availableMatragics(index: number = 0) {
        let res = [];
        this.matragics.forEach((card) => {
            res.push(card.available(index, 'filed-effect'));
        })

        return res;
    }
}




export interface Cemetery {
    cards: Card[];
    owner: User;
}

export class Cemetery {
    name = 'cemetery';
    constructor(owner: User) {
        this.cards = [];
        this.owner = owner;
    }
    async recieve(cards: Card[], caller) {
        this.cards = this.cards.concat(cards);
        await this.owner.duel.triger('into-cemetery', caller);
    }
}




export interface Event {
    subscriber: any;
    name: string;
    callback: Function;
    symbol?: Symbol;
}




export interface Duel {
    users: User[];
}

export class Duel {
    events = new Map();
    constructor(users: User[] = []) {
        this.users = users;

        this.listen();
    }
    loadDesks(myDesk: cardMold[], enemyDesk: cardMold[]) {
        this.users[0].loadDesk(myDesk);
        this.users[1].loadDesk(enemyDesk);
    }
    get me(): User {
        return this.users[0];
    }
    get enemy(): User {
        return this.users[1];
    }
    myEnemy(user) {
        if (user === this.me)  {
            return this.enemy;
        } else {
            return this.me;
        }
    }
    msg(msg: string, speaker?: User) {
        if (speaker) {
            console.log(speaker.name + ': ' + msg);
        } else {
            console.log(msg);
        }
    }
    addEvent(event: Event) {
        if (!this.events.has(event.name)) {
            this.events.set(event.name, new Map());
        }

        let symbol = Symbol();
        event.symbol = symbol;
        this.events.get(event.name).set(symbol, event);

        return symbol;
    }
    delEvent(eventName: String, symbol: Symbol) {
        if (this.events.has(eventName)) {
            this.events.get(eventName).delete(symbol);
        }
    }
    async triger(eventName: string, caller: any, ...args: any) {
        if (this.events.has(eventName)) {
            for (let event of this.events.get(eventName).values()) {
                event.callback(caller, event, ...args);
            }
        }
    }
    loose(who: User, type: string, msg: string) {
        this.msg(msg);
    }
    sendAction(action: string, ...args) {
        return new Promise(resolve => {
            window.addEventListener('message', (answer) => {
                this.listen();
                resolve(answer);
            });
            window.postMessage({
                type: 'ASK',
                action,
                args: { ...args }
            }, '*');
        })
    }
    listen() {
        window.addEventListener('message', (msg) => {
            if (msg.data.type === 'ASK') {
                window.postMessage({
                    type: 'ANSWER'
                }, '*')
            }
        })
    }
    async pipeline(action: string, ...args: any) {
        // let answer = await this.sendAction(action, ...args);
        return function() {}
    }
}




export var createDuel = function(myName: string, enemyName: string) {
    const duel = new Duel();
    const me = new User(duel, myName);
    const enemy = new User(duel, enemyName);

    createDuel = function() {
        return duel;
    }

    return duel;
};